mod res;
mod sys;
mod ui;
mod validators;

use std::sync::mpsc::channel;
use specs::{World, DispatcherBuilder, Builder};

use crate::res::{
    position_storage::Position,
    velocity_storage::Velocity,
    observing_storage::Observing,
    minions_storage::Minions,
    overlord_storage::Overlord,
};

use crate::sys::{
    get_system::{
        GetSystem,
        GetMessage,
    },
    get_stream_system::{
        GetStreamSystem,
        GetStreamMessage,
    },
    set_system::{
        SetMessage,
        SetSystem,
    }
};

use crate::validators::{
    minionship_validator::MinionshipValidator,
    self_validator::SelfValidator,
    observing_validator::ObservingValidator,
    or_validator::OrValidator,
};

use crate::sys::interface::{
    QueryMinionsPositionStreamMessage,
    QueryMinionsPositionStreamSystem,
    QueryMinionsPositionMessage,
    QueryMinionsPositionSystem,
    QueryObservingPositionStreamMessage,
    QueryObservingPositionStreamSystem,
    QueryObservingPositionMessage,
    QueryObservingPositionSystem,
    QuerySelectedPositionStreamMessage,
    QuerySelectedPositionStreamSystem,
    QuerySelectedPositionMessage,
    QuerySelectedPositionSystem,
    SetVelocityMessage,
    SetVelocitySystem,
};

fn main() {
    let (query_selected_position_sn, query_selected_position_rc) = channel::<QuerySelectedPositionMessage>();
    let (query_selected_position_stream_sn, query_selected_position_stream_rc) = channel::<QuerySelectedPositionStreamMessage>();
    let (query_minions_position_sn, query_minions_position_rc) = channel::<QueryMinionsPositionMessage>();
    let (query_minions_position_stream_sn, query_minions_position_stream_rc) = channel::<QueryMinionsPositionStreamMessage>();
    let (query_observing_position_sn, query_observing_position_rc) = channel::<QueryObservingPositionMessage>();
    let (query_observing_position_stream_sn, query_observing_position_stream_rc) = channel::<QueryObservingPositionStreamMessage>();
    let (set_velocity_sn, set_velocity_rc) = channel::<SetVelocityMessage>();

    let (position_sn, position_rc) = channel::<GetMessage<Position>>();
    let (position_stream_sn, position_stream_rc) = channel::<GetStreamMessage<Position>>();
    let (velocity_sn, velocity_rc) = channel::<SetMessage<Velocity>>();
    let (observe_sn, observe_rc) = channel::<GetMessage<Observing>>();
    let (own_sn, own_rc) = channel::<GetMessage<Minions>>();
    let (join_sn, join_rc) = channel::<sys::player_join_system::JoinMessage>();
    let (spawn_sn, spawn_rc) = channel::<sys::spawn_system::SpawnMessage>();

    let mut world = World::new();
    world.register::<Position>();
    world.register::<Velocity>();
    world.register::<Overlord>();
    world.register::<Minions>();
    world.register::<Observing>();

    world.add_resource(res::delta_time::DeltaTime(0.2));

    // generate entities with position for quadtree system testing
    if true
    {
        //   x  |   x
        //  x   | x
        //    x |   x
        // -----+-----
        //    x |x
        //  x   |    x
        //   x  |  x
        // generate on each quadtrant 3 entities with varying positions
        // With this setup, the root node of the quad tree should have 4 leaf children.

        // EDIT: now there are owner entities too. They are capable of storing the
        // entities, which can be seen by their owned entities.

        let owner1 = world.create_entity()
            .with(Observing(Vec::new()))
            .build();

        let owner2 = world.create_entity()
            .with(Observing(Vec::new()))
            .build();

        let owner3 = world.create_entity()
            .with(Observing(Vec::new()))
            .build();

        world.create_entity()
            .with(Position{ x: 5.0, y: 5.0})
            .with(Overlord(owner1.id()))
            .build();
        world.create_entity()
            .with(Position{ x: 1.0, y: 2.0})
            .with(Overlord(owner1.id()))
            .build();
        world.create_entity()
            .with(Position{ x: 7.0, y: 3.0})
            .with(Overlord(owner1.id()))
            .build();

        world.create_entity()
            .with(Position{ x: -5.0, y: 5.0})
            .with(Overlord(owner2.id()))
            .build();
        world.create_entity()
            .with(Position{ x: -1.0, y: 2.0})
            .with(Overlord(owner2.id()))
            .build();
        world.create_entity()
            .with(Position{ x: -7.0, y: 3.0})
            .with(Overlord(owner2.id()))
            .build();

        world.create_entity()
            .with(Position{ x: 5.0, y: -5.0})
            .with(Overlord(owner2.id()))
            .build();
        world.create_entity()
            .with(Position{ x: 1.0, y: -2.0})
            .with(Overlord(owner2.id()))
            .build();
        world.create_entity()
            .with(Position{ x: 7.0, y: -3.0})
            .with(Overlord(owner2.id()))
            .build();

        world.create_entity()
            .with(Position{ x: -5.0, y: -5.0})
            .with(Overlord(owner3.id()))
            .build();
        world.create_entity()
            .with(Position{ x: -1.0, y: -2.0})
            .with(Overlord(owner3.id()))
            .build();
        world.create_entity()
            .with(Position{ x: -7.0, y: -3.0})
            .with(Overlord(owner3.id()))
            .build();
    }

    let mut dispatcher = DispatcherBuilder::new()
        // Interface set systems
        .with(SetVelocitySystem::new(set_velocity_rc), "set_velocity_system", &[])
        // Core systems
        .with(sys::movement_system::MovementSystem, "movement_system", &["set_velocity_system"])
        .with(sys::observe_system::ObserveSystem, "observe_system", &["movement_system"])
        // Interface query systems
        .with(
            QuerySelectedPositionSystem::new(query_selected_position_rc), 
            "query_selected_position_system", 
            &["movement_system"]
        )
        .with(
            QuerySelectedPositionStreamSystem::new(query_selected_position_stream_rc), 
            "query_selected_position_stream_system", 
            &["movement_system"]
        )
        .with(
            QueryMinionsPositionSystem::new(query_minions_position_rc), 
            "query_minions_position_system", 
            &["movement_system"]
        )
        .with(
            QueryMinionsPositionStreamSystem::new(query_minions_position_stream_rc), 
            "query_minions_position_stream_system", 
            &["movement_system"]
        )
        .with(
            QueryObservingPositionSystem::new(query_observing_position_rc), 
            "query_observing_position_system",
            &["movement_system"]
        )
        .with(
            QueryObservingPositionStreamSystem::new(query_observing_position_stream_rc), 
            "query_observing_position_stream_system", 
            &["movement_system"]
        )
        // Other systems
        .with(sys::player_join_system::PlayerJoinSystem::new(join_rc), "player_join_system", &[])
        .with(sys::spawn_system::SpawnSystem::new(spawn_rc), "spawn_system", &[])
        .build();
    
    // spawn thread for handling incoming requests
    std::thread::spawn(move || {
        ui::handle_connections(
            query_selected_position_sn,
            query_selected_position_stream_sn,
            query_minions_position_sn,
            query_minions_position_stream_sn,
            query_observing_position_sn,
            query_observing_position_stream_sn,
            set_velocity_sn,
            join_sn,
            spawn_sn
        );
    });

    loop {
        use std::time::Duration;

        dispatcher.dispatch(&mut world.res);
        world.maintain();

        std::thread::sleep(Duration::from_millis(200));
    }
}
